{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与函数类似，类也可以用一个或多个类型参数化。用于管理特定类型元素的容器类就是个例子。\n",
    "\n",
    "通过使用类模板，可以在元素类型开放的情况下实现容器类。\n",
    "\n",
    "本章中，我们将使用堆栈作为类模板的示例。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.1 实现栈类模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "source": [
    "与函数模板一样，在头文件中声明并定义 `Stack<>` 类，如下所示:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// http://www.tmplbook.com/code/basics/stack1.hpp.html\n",
    "\n",
    "#include <cassert>\n",
    "#include <vector>\n",
    "\n",
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "};\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::push(T const& elem) {\n",
    "    elems.push_back(elem);  // append copy of passed elem\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::pop() {\n",
    "    assert(!elems.empty());\n",
    "    elems.pop_back();  // remove last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "T const& Stack<T>::top() const {\n",
    "    assert(!elems.empty());\n",
    "    return elems.back();  // return copy of last element\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类模板使用 C++ 标准库的 `vector<>` 实现。因此，不必实现内存管理、复制构造函数和赋值操作符，可以专注于该类模板的接口。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 声明类模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "声明类模板类似于声明函数模板。声明前，必须将一个或多个标识符声明为类型参数。同样，`T`作为常用标识符:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <class T>\n",
    "class Stack {\n",
    "  // ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，关键字 `class` 也可以替换 `typename`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "class Stack {\n",
    "  // ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类模板内部，可以像使用其他类型一样使用 `T` 来声明成员和成员函数。\n",
    "\n",
    "这个例子中，`T` 用来声明元素的类型为 `T` 的 `vector`，将 `push()` 声明为使用 `T` 作参数的成员函数，并将 `top()` 声明为返回`T`的函数:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个类的类型是 `Stack<T>`，`T` 是模板参数。因此，只要在使用该类的类型，就必须使用 `Stack<T>`声明，可以推导模板参数的情况除外。\n",
    "\n",
    "但在类模板中使用类名 (不带模板参数)，表明这个内部类的模板参数类型和模板类的参数类型相同 (详见第 13.2.3 节)。\n",
    "\n",
    "例如，必须声明自定义的复制构造函数和赋值操作符:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "class Stack {\n",
    "  // ... \n",
    "  Stack (Stack const&); // copy constructor\n",
    "  Stack& operator= (Stack const&); // assignment operator\n",
    "  // ... \n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "形式上等价于:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "class Stack {\n",
    "  // ... \n",
    "  Stack (Stack<T> const&); // copy constructor\n",
    "  Stack<T>& operator= (Stack<T> const&); // assignment operator\n",
    "  // ... \n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通常 `<T>` 表示特殊模板参数，所以最好使用第一种方式。\n",
    "\n",
    "这里，"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "bool operator== (Stack<T> const& lhs, Stack<T> const& rhs);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在需要类名而不是类类型的地方，只能使用 `Stack`。尤其是，指定构造函数的名称 (而不是参数)和析构函数时。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与非模板类不同，不能在函数或块作用域内声明或定义类模板。通常，模板只能在全局/命名空间作用域或类中声明内部定义 (参见 12.1 节了解详细信息)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 成员函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "要定义类模板的成员函数，必须将其指定为模板，并且使用类模板对类型进行限定。\n",
    "\n",
    "因此，`Stack<T>` 类型的成员函数 `push()` 实现如下所示:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "void Stack<T>::push (T const& elem)\n",
    "{\n",
    "    elems.push_back(elem); // append copy of passed elem\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 `vector` 的 `push_back()`，将元素添加到 `vector` 的末尾。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，`vector` 的 `pop_back()` 会删除最后一个元素，但不返回这个元素，这种行为是异常安全的。使用 `pop()` 的完全异常安全版本，来返回删除元素是不可能的 (这个问题由 Tom Cargill 在[CargillExceptionSafety] 中首次提出，并在 [sutterexception] 中的第 10 项中进行讨论)。这里可以忽略这个危险，可以实现 `pop()` 返回刚刚删除的元素。为此，只需使用 `T` 声明元素类型的局部变量即可:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> https://cppreference.com/w/cpp/container/vector.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "T Stack<T>::pop ()\n",
    "{\n",
    "    assert(!elems.empty());\n",
    "    T elem = elems.back(); // save copy of last element\n",
    "    elems.pop_back();      // remove last element\n",
    "    return elem;           // return copy of saved element\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当 `vector` 中没有元素时，`back()`(返回最后一个元素) 和 `pop_back()`(删除最后一个元素) 会导致未定义行为，所以先检查堆栈是否为空。如果为空，则触发断言，因为在空堆栈上调用 `pop()` 是一个使用错误。`top()` 也是这样，当试图删除一个不存在的 `top` 元素时，`top()` 会返回但不删除 `top` 元素:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "T const& Stack<T>::top () const\n",
    "{\n",
    "    assert(!elems.empty());\n",
    "    return elems.back(); // return last element\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当然，对于成员函数，可以在类声明中以内联的形式，实现类模板的成员函数。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack {\n",
    "    // ...\n",
    "    void push (T const& elem) {\n",
    "        elems.push_back(elem); // append copy of passed elem\n",
    "    }\n",
    "    // ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.2 使用栈类模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++17 前，要使用类模板的对象，必须显式指定模板参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> C++17 引入了类参数模板推导，若模板参数可以从构造函数派生，则可以跳过这些参数。这会在第 2.9 节中进行讨论。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面的例子展示了如何使用类模板 `Stack<>`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// http://www.tmplbook.com/code/basics/stack1.hpp.html\n",
    "\n",
    "#include <cassert>\n",
    "#include <vector>\n",
    "\n",
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "};\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::push(T const& elem) {\n",
    "    elems.push_back(elem);  // append copy of passed elem\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::pop() {\n",
    "    assert(!elems.empty());\n",
    "    elems.pop_back();  // remove last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "T const& Stack<T>::top() const {\n",
    "    assert(!elems.empty());\n",
    "    return elems.back();  // return copy of last element\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7\n",
      "hello\n"
     ]
    }
   ],
   "source": [
    "// http://www.tmplbook.com/code/basics/stack1test.cpp.html\n",
    "\n",
    "#include <iostream>\n",
    "#include <string>\n",
    "\n",
    "{\n",
    "  Stack<int>         intStack;       // stack of ints\n",
    "  Stack<std::string> stringStack;    // stack of strings\n",
    "\n",
    "  // manipulate int stack\n",
    "  intStack.push(7);\n",
    "  std::cout << intStack.top() << '\\n';\n",
    "\n",
    "  // manipulate string stack\n",
    "  stringStack.push(\"hello\");\n",
    "  std::cout << stringStack.top() << '\\n';\n",
    "  stringStack.pop();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过声明类型 `Stack<int>`，`int` 在类模板中作为类型 `T`。因此，`intStack` 是一个对象，使用的是`vector<int>` 类型，并且调用相应的成员函数。类似地，通过声明和使用 `Stack<std::string>`，将创建使用 `vector<std::string>` 的对象，使用相应的成员函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，代码只对调用的模板 (成员) 函数实例化。对于类模板，只有在使用成员函数时才实例化。当然，这节省了时间和空间，并且只允许使用部分地类模板，这会在 2.3 节中详细讨论。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "例子中，默认构造函数 `push()` 和 `top() `为 `int` 和 `string` 实例化，但 `pop()` 只对 `string` 实例化。如果类模板具有静态成员，则对于使用类模板的每个类型实例，这些成员也会实例化一次。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> https://cppinsights.io/s/0550fab7 没有实例化`int`版本的`pop`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以像使用其他类型一样使用实例化的类模板类型。可以使用 `const`、`volatile` 或从中派生数组和引用类型对其进行限定。也可以将其作为类型定义的一部分，使用 `typedef` 或 `using`(请参阅第 2.8节了解关于类型定义的详细信息)，或者在构建另一个模板类型时将其用作类型参数。\n",
    "\n",
    "例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void foo(Stack<int> const& s) // parameter s is int stack\n",
    "{\n",
    "    using IntStack = Stack<int>; // IntStack is another name for Stack<int>\n",
    "    Stack<int> istack[10];       // istack is array of 10 int stacks\n",
    "    IntStack istack2[10];        // istack2 is also an array of 10 int stacks (same type)\n",
    "    // ...\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "模板参数可以是任何类型，例如 `float` 指针，甚至是 `Stack<int>`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "Stack<float*> floatPtrStack; // stack of float pointers\n",
    "Stack<Stack<int>> intStackStack; // stack of stack of ints"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里的要求是，需要这种类型支持所使用的操作。\n",
    "注意，在 C++11 之前，必须在两个模板右括号之间放空格:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "Stack<Stack<int> > intStackStack; // 所有C++版本都可以用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果没有这样做，就使用 `>>`，会导致语法错误:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "Stack<Stack<int>> intStackStack; // C++11之前会报错"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "旧行为可以帮助 C++ 编译器对独立于代码语义源码进行标记。然而，由于缺少空格是一个错误，这需要相应的错误消息，因此无论如何都必须考虑代码的语义。因此，在 C++11 中，在两个模板右括号之间放一个空格的规则被“尖括号黑客”删除了 (详见 13.3.1 节)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.3 部分使用栈类模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类模板通常对其实例化的模板参数使用多个操作 (包括构造和析构)。这可能会使，这些模板参数必须为所有成员函数，提供所有必要的操作，而模板参数只需要提供所需的必要操作 (而不是可能需要) 即可。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "例如，类 `Stack<>` 提供成员函数 `printOn()` 打印堆栈中的全部内容，并对每个元素使用操作符`<<`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack {\n",
    "    // ...\n",
    "    void printOn(std::ostream& strm) const {\n",
    "        for (T const& elem : elems) {\n",
    "            strm << elem << ' '; // 对每个元素使用<<\n",
    "        }\n",
    "    }\n",
    "};"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// http://www.tmplbook.com/code/basics/stack1.hpp.html\n",
    "\n",
    "#include <cassert>\n",
    "#include <vector>\n",
    "#include <ostream>\n",
    "\n",
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "    void printOn(std::ostream& strm) const;\n",
    "};\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::push(T const& elem) {\n",
    "    elems.push_back(elem);  // append copy of passed elem\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::pop() {\n",
    "    assert(!elems.empty());\n",
    "    elems.pop_back();  // remove last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "T const& Stack<T>::top() const {\n",
    "    assert(!elems.empty());\n",
    "    return elems.back();  // return copy of last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::printOn(std::ostream& strm) const {\n",
    "    for (T const& elem : elems) {\n",
    "        strm << elem << ' '; // 对每个元素使用<<\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "7\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "@0x73abd1e29540"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#include <iostream>\n",
    "\n",
    "Stack<std::pair<int, int>> ps; // 注意：std::pair<> 不支持 <<\n",
    "ps.push({4, 5}); // OK\n",
    "ps.push({6, 7}); // OK\n",
    "\n",
    "std::cout << ps.top().first << '\\n'; // OK\n",
    "std::cout << ps.top().second << '\\n'; // OK"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "只在使用 `printOn()` 时代码才会出错，因为不能实例化特定元素类型的操作符 `<<`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "ps.printOn(std::cout); // error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const std::pair<int, int>')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 概念"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "先来看一个问题: 如何知道模板需要哪些操作才能实例化?“概念”通常用来表示模板库中需要的约束。例如，C++ 标准库依赖于随机访问迭代器和默认可构造函数等概念。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "目前 (如 C++17)，概念只能通过文字进行表达 (如代码注释)。这可能会成为一个严重的问题，因为不遵守约束可能会导致大量的错误消息 (参见 9.4)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "多年来，也有一些方法和试验支持将概念定义和验证为一种语言特性。然而，到 C++17 为止，还这样的方法还没标准化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从 C++11 开始，可以通过使用 `static_assert` 和预定义类型特征来检查约束。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class C\n",
    "{\n",
    "    static_assert(std::is_default_constructible<T>::value, \"Class C requires default-constructible elements\");\n",
    "    // ...\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用默认构造函数，若没有这个断言，编译会失败。然而，错误消息描述的是整个模板实例化的过程，从最初的实例化原因到检测到错误的实际模板的定义 (参见章节 9.4)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但需要更复杂的代码来检查，例如:`T` 类型的对象是够提供了特定的成员函数，或者是否有可用的小于操作符。相关的示例，请参见第 19.6.3 节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有关 C++ 概念的详细讨论，请参阅附录 E。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.4 友元"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 `printOn()` 打印堆栈内容，不如为堆栈实现 `<<` 操作符。然而，通常 `<<` 操作符会实现为非成员函数，然后内联调用 `printOn()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack {\n",
    "    // ...\n",
    "    void printOn(std::ostream& strm) const {\n",
    "        // ...\n",
    "    }\n",
    "\n",
    "    friend std::ostream& operator<<(std::ostream& strm, Stack<T> const& s) {\n",
    "        s.printOn(strm);\n",
    "        return strm;\n",
    "    }\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这意味着用于类 `Stack<>` 的 `<<` 操作符不是一个函数模板，而是在需要时用类模板实例化的“普通”函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 其是一个模板实体，参见 12.1 节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但当试图声明友元函数并实现时，事情会变得更加复杂。这里有两种选择:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 隐式声明一个新的函数模板，但要使用不同的模板参数，比如 `U`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack {\n",
    "    // ...\n",
    "    template<typename U>\n",
    "    friend std::ostream& operator<<(std::ostream& strm, Stack<U> const&);\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再次使用 `T` 或跳过模板参数都不起作用 (要么内部的 `T` 隐藏外部的 `T`，要么在命名空间作用域中声明一个非模板函数)。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <cassert>\n",
    "#include <vector>\n",
    "#include <ostream>\n",
    "\n",
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "    void printOn(std::ostream& strm) const;\n",
    "    template<typename U>\n",
    "    friend std::ostream& operator<<(std::ostream& strm, Stack<U> const&);\n",
    "};\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::push(T const& elem) {\n",
    "    elems.push_back(elem);  // append copy of passed elem\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::pop() {\n",
    "    assert(!elems.empty());\n",
    "    elems.pop_back();  // remove last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "T const& Stack<T>::top() const {\n",
    "    assert(!elems.empty());\n",
    "    return elems.back();  // return copy of last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::printOn(std::ostream& strm) const {\n",
    "    for (T const& elem : elems) {\n",
    "        strm << elem << ' '; // 对每个元素使用<<\n",
    "    }\n",
    "}\n",
    "\n",
    "template<typename U>\n",
    "std::ostream& operator<<(std::ostream& strm, Stack<U> const& s) {\n",
    "    s.printOn(strm);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "7\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "@0x7091be629540"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#include <iostream>\n",
    "\n",
    "Stack<std::pair<int, int>> ps;  // std::pair<> has no operator<< defined\n",
    "\n",
    "ps.push({4, 5});  // OK\n",
    "ps.push({6, 7});  // OK\n",
    "\n",
    "std::cout << ps.top().first << '\\n';  // OK\n",
    "std::cout << ps.top().second << '\\n'; // OK"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::cout << ps << '\\n';  // error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'const std::pair<int, int>')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 可以将 `Stack<T>` 的输出操作符转发声明为模板，这样就需要转发声明 `Stack<T>`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack;\n",
    "\n",
    "template<typename T>\n",
    "std::ostream& operator<< (std::ostream&, Stack<T> const&);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然后，将该函数声明为友元:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "class Stack {\n",
    "    // ...\n",
    "    friend std::ostream& operator<< <T> (std::ostream&, Stack<T> const&);\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意“函数名”操作符 `<<` 后面的 `<T>`。因此，需要将非成员函数模板的特化声明为友元。若没有 `<T>`，需要声明新的非模板函数。详细信息请参见 12.5.2。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其他情况下，可以对没有定义 `<<` 操作符的元素使用这个类。只有对这个堆栈调用操作符 `<<` 时才会出现错误:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <cassert>\n",
    "#include <vector>\n",
    "#include <ostream>\n",
    "\n",
    "template<typename T>\n",
    "class Stack;\n",
    "\n",
    "template<typename T>\n",
    "std::ostream& operator<< (std::ostream&, Stack<T> const&);\n",
    "\n",
    "template <typename T>\n",
    "class Stack {\n",
    "   private:\n",
    "    std::vector<T> elems;  // elements\n",
    "\n",
    "   public:\n",
    "    void push(T const& elem);  // push element\n",
    "    void pop();                // pop element\n",
    "    T const& top() const;      // return top element\n",
    "    bool empty() const {       // return whether the stack is empty\n",
    "        return elems.empty();\n",
    "    }\n",
    "    void printOn(std::ostream& strm) const;\n",
    "    friend std::ostream& operator<< <T> (std::ostream&, Stack<T> const&);\n",
    "};\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::push(T const& elem) {\n",
    "    elems.push_back(elem);  // append copy of passed elem\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::pop() {\n",
    "    assert(!elems.empty());\n",
    "    elems.pop_back();  // remove last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "T const& Stack<T>::top() const {\n",
    "    assert(!elems.empty());\n",
    "    return elems.back();  // return copy of last element\n",
    "}\n",
    "\n",
    "template <typename T>\n",
    "void Stack<T>::printOn(std::ostream& strm) const {\n",
    "    for (T const& elem : elems) {\n",
    "        strm << elem << ' '; // 对每个元素使用<<\n",
    "    }\n",
    "}\n",
    "\n",
    "template<typename T>\n",
    "std::ostream& operator<< (std::ostream& strm, Stack<T> const& s) {\n",
    "    s.printOn(strm);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "7\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "@0x742d8f829540"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#include <iostream>\n",
    "\n",
    "Stack<std::pair<int, int>> ps;  // std::pair<> has no operator<< defined\n",
    "\n",
    "ps.push({4, 5});  // OK\n",
    "ps.push({6, 7});  // OK\n",
    "\n",
    "std::cout << ps.top().first << '\\n';  // OK\n",
    "std::cout << ps.top().second << '\\n'; // OK"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::cout << ps << '\\n';  // error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'const std::pair<int, int>')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.5 类模板的特化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以为某些模板参数特化类模板。类似于函数模板的重载 (参见第 1.5 节)，特化类模板允许优化特定类型的实现，或者为类模板的实例化修复特定类型的错误行为。但若特化类模板，则必须特化所有成员函数。虽然可以特化类模板的单个成员函数，但若这样做了，就不能再特化该特化成员所属的整个类模板实例。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "要特化类模板，必须用 `template<>` 和类模板特化的类型声明类。这些类型可用作模板参数，必须直接在类名之后指定:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.6 偏特化"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "c++",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
